﻿
namespace Patterns4Net.Core.CecilExtensions
{
    using System.Diagnostics.Contracts;
    using Mono.Cecil;
    using Mono.Cecil.Cil;

    public static class InstructionsExtensions
    {
        public static Instruction Normalize(this Instruction instruction, MethodBody methodBody)
        {
            Contract.Requires(instruction != null);
            Contract.Requires(methodBody != null);

            if (instruction.OpCode == OpCodes.Stloc_0)
            {
                return Instruction.Create(OpCodes.Stloc, methodBody.Variables[0]);
            }
            else if (instruction.OpCode == OpCodes.Stloc_1)
            {
                return Instruction.Create(OpCodes.Stloc, methodBody.Variables[1]);
            }
            else if (instruction.OpCode == OpCodes.Stloc_2)
            {
                return Instruction.Create(OpCodes.Stloc, methodBody.Variables[2]);
            }
            else if (instruction.OpCode == OpCodes.Stloc_3)
            {
                return Instruction.Create(OpCodes.Stloc, methodBody.Variables[3]);
            }
            else if (instruction.OpCode == OpCodes.Stloc_3)
            {
                return Instruction.Create(OpCodes.Stloc, instruction.Operand as VariableDefinition);
            }

            if (instruction.OpCode == OpCodes.Ldloc_0)
            {
                return Instruction.Create(OpCodes.Ldloc, methodBody.Variables[0]);
            }
            else if (instruction.OpCode == OpCodes.Ldloc_1)
            {
                return Instruction.Create(OpCodes.Ldloc, methodBody.Variables[1]);
            }
            else if (instruction.OpCode == OpCodes.Ldloc_2)
            {
                return Instruction.Create(OpCodes.Ldloc, methodBody.Variables[2]);
            }
            else if (instruction.OpCode == OpCodes.Ldloc_3)
            {
                return Instruction.Create(OpCodes.Ldloc, methodBody.Variables[3]);
            }
            else if (instruction.OpCode == OpCodes.Ldloc_S)
            {
                return Instruction.Create(OpCodes.Ldloc, instruction.Operand as VariableDefinition);
            }

            if (instruction.OpCode == OpCodes.Ldarg_0)
            {
                return Instruction.Create(OpCodes.Ldarg, GetParameter(0, methodBody.Method));
            }
            else if (instruction.OpCode == OpCodes.Ldarg_1)
            {
                return Instruction.Create(OpCodes.Ldarg, GetParameter(1, methodBody.Method));
            }
            else if (instruction.OpCode == OpCodes.Ldarg_2)
            {
                return Instruction.Create(OpCodes.Ldarg, GetParameter(2, methodBody.Method));
            }
            else if (instruction.OpCode == OpCodes.Ldarg_3)
            {
                return Instruction.Create(OpCodes.Ldarg, GetParameter(3, methodBody.Method));
            }
            else if (instruction.OpCode == OpCodes.Ldarg_S)
            {
                return Instruction.Create(OpCodes.Ldarg, instruction.Operand as ParameterDefinition);
            }

            return instruction;
        }

        public static OpCode NormalizedOpCode(this Instruction instruction)
        {
            switch (instruction.OpCode.Code)
            {
                case Code.Ldarg:
                case Code.Ldarg_0:
                case Code.Ldarg_1:
                case Code.Ldarg_2:
                case Code.Ldarg_3:
                case Code.Ldarg_S:
                    return OpCodes.Ldarg;
                case Code.Stloc:
                case Code.Stloc_0:
                case Code.Stloc_1:
                case Code.Stloc_2:
                case Code.Stloc_3:
                case Code.Stloc_S:
                    return OpCodes.Stloc;
                case Code.Ldloc:
                case Code.Ldloc_0:
                case Code.Ldloc_1:
                case Code.Ldloc_2:
                case Code.Ldloc_3:
                case Code.Ldloc_S:
                    return OpCodes.Ldloc;
                default:
                    return instruction.OpCode;
            }
        }

        public static bool IsCallInstruction(this Instruction instruction)
        {
            return instruction.OpCode == OpCodes.Call ||
                instruction.OpCode == OpCodes.Calli ||
                instruction.OpCode == OpCodes.Callvirt;
        }

        /// <summary>
        /// When we have an instance method, then the index 0 means this parameter and other 
        /// indexes are shifted by one.
        /// </summary>
        private static ParameterDefinition GetParameter(int index, MethodDefinition method)
        {
            if (method.HasThis)
            {
                if (index == 0)
                {
                    return method.Body.ThisParameter;
                }

                return method.Parameters[index - 1];
            }
            else
            {
                return method.Parameters[index];
            }
        }
    }
}
